package com.good.shopping.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.good.shopping.product.service.CategoryBrandRelationService;
import com.good.shopping.product.vo.Catelog2Vo;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.good.common.utils.PageUtils;
import com.good.common.utils.Query;

import com.good.shopping.product.dao.CategoryDao;
import com.good.shopping.product.entity.CategoryEntity;
import com.good.shopping.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    // 品牌分类关联
    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;


    // redis
    @Autowired
    StringRedisTemplate redisTemplate;

    // 分布式锁
    @Autowired
    RedissonClient redisson;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }


    /**
     * 列表树
     *
     * @return {@link List}<{@link CategoryEntity}>
     */
    @Override
    public List<CategoryEntity> listTree() {
        List<CategoryEntity> list = baseMapper.selectList(null);
        List<CategoryEntity> listTree = list.stream()
                .filter(x -> 0 == x.getParentCid())
                .map(x -> {
                    x.setChildren(doSetChildren(list,x));
                    return x;
                })
                .sorted((a, b) -> {
                    return (a.getSort() == null ? 0 : a.getSort()) - (b.getSort() == null ? 0 : b.getSort());
                })
                .collect(Collectors.toList());
        return listTree;
    }

    // 递归查询
    private List<CategoryEntity> doSetChildren(List<CategoryEntity> list,CategoryEntity pojo){
        List<CategoryEntity> children = list.stream()
                .filter(x -> x.getParentCid().equals(pojo.getCatId()))
                .peek(x -> x.setChildren(doSetChildren(list, x)))
                .sorted((a, b) -> {
                    return (a.getSort() == null ? 0 : a.getSort()) - (b.getSort() == null ? 0 : b.getSort());
                })
                .collect(Collectors.toList());
        return children;
    }

    // 逻辑删除
    @Transactional
    @Override
    public void removeFakeByIds(List<Long> asList) {
        // todo 1.是否被其他数据引用
        // 批量逻辑删除
        baseMapper.deleteBatchIds(asList);
    }


    // 获取三级分类数组
    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        List<Long> parentPath = findParentPath(catelogId, paths);
        // 反转排列
        Collections.reverse(parentPath);
        // list 转数组
        Long[] longs = parentPath.toArray(new Long[parentPath.size()]);
        return longs;
    }
    // 递归查询【父,子,孙】
    private List<Long> findParentPath(Long catelogId, List<Long> paths) {
        paths.add(catelogId);
        CategoryEntity pojo = this.getById(catelogId);
        if (pojo.getParentCid() != 0) {
            findParentPath(pojo.getParentCid(), paths);
        }
        return paths;
    }

    /**
     * 修改 - 级联更新所有关联的数据
     *   更新品牌表，在其他关联表，有冗余字段，也需要更新
     *
     * 删除缓存
     *  @CacheEvict:失效模式
     *  @CachePut:双写模式，需要有返回值
     *  1、同时进行多种缓存操作：@Caching
     *  2、指定删除某个分区下的所有数据 @CacheEvict(value = "category",allEntries = true)
     *  3、存储同一类型的数据，都可以指定为同一分区
     */
    // @Caching(evict = {
    //         @CacheEvict(value = "category",key = "'getLevel1Categorys'"),
    //         @CacheEvict(value = "category",key = "'getCatalogJson'")
    // }) 组合写法
    // @CacheEvict(value = "category",key = "'getLevel1Categorys'") // 失效模式 - 清除 getLevel1Categorys下的缓存 -会清除对应的缓存
    @CacheEvict(value = "category",allEntries = true)  //删除某个分区下的所有数据
    @Transactional
    @Override
    public void updateDetail(CategoryEntity category) {
        this.updateById(category);
        if (!StringUtils.isEmpty(category.getName())) {
            // TODO 更新其他关联
            // 更新品牌分类关联 的分类字段
            categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
        }
        // 同时更新缓存中的数据  - 使用注解 不需要手动写失效代码
        // redisTemplate.delete("catalogJSON"); // 失效模式
    }


    // 查看耗时
    // 每个缓存都有自己的名字【缓存的分区（按业务类型区分）】  @Cacheable({"categiry","bb"})
    // 缓存value值 （默认是jdk序列化机制）key值 (表达式 字符串要加'')  @Cacheable(value = "categiry",key = "'Level1Categorys'")
    // #root.method.name 获取当前方法名
    @Cacheable(value = "category",key = "#root.method.name")
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
        long l = System.currentTimeMillis();
        // 查询一级分类
        List<CategoryEntity> categoryEntities =
                baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        System.out.println("消耗时间，" + (System.currentTimeMillis() - 1));
        return categoryEntities;
    }


    // 优化三级分类
    //    优化1次   改为只查询数据库一次，多次递归
    //    优化2次   使用Redis缓存
    //    优化3次   使用分布式锁 （可重入锁  读写锁）
    //    优化4次   使用缓存注解
    // 缓存 sync 执行本地锁
    @Cacheable(value = "category",key = "#root.methodName",sync = true)
    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {

        // 只查询一次数据库 - 其他的都走缓存
        System.out.println("查询了数据库 -----"+ Thread.currentThread().getName());

        // 查询全部  只查询一次数据库
        List<CategoryEntity> selectList = baseMapper.selectList(null);
        // System.out.println(selectList);
        // 原先的  List<CategoryEntity> level1Categorys = getLevel1Categorys();
        // 查询所有一级分类 （参数1 全部集合 参数2 一级id）
        List<CategoryEntity> level1Category = getParent_cid(selectList, 0L);

        // 2 封装数据
        Map<String, List<Catelog2Vo>> parent_cid = level1Category.stream()
                .collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
                            // 1 每一个的一级分类，查到这个一级分类的二级分类
                            List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
                            // 2 分装上面的结果
                            List<Catelog2Vo> catelog2Vos = null;
                            if (categoryEntities != null) {
                                catelog2Vos = categoryEntities.stream().map(l2 -> {
                                    Catelog2Vo catelog2Vo = new Catelog2Vo(
                                            v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                                    // 1 找当前二级分类的三级分类封装成vo
                                    List<CategoryEntity> level3Catelog = getParent_cid(selectList, l2.getCatId());
                                    if (level3Catelog != null) {
                                        List<Catelog2Vo.Catelog3Vo> collect = level3Catelog.stream().map(l3 -> {
                                            // 2 分装成指定格式
                                            Catelog2Vo.Catelog3Vo catelog3Vo =
                                                    new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                                            return catelog3Vo;
                                        }).collect(Collectors.toList());
                                        catelog2Vo.setCatalog3List(collect);
                                    }
                                    return catelog2Vo;
                                }).collect(Collectors.toList());
                            }
                            return catelog2Vos;
                        }
                ));

        // 3 查到的数据放入缓存，将对象转为json放在缓存中  有效期为1天
//        String jsonString = JSON.toJSONString(parent_cid);
//        redisTemplate.opsForValue().set("catalogJSON",jsonString,1,TimeUnit.DAYS);

        return parent_cid;

    }


    // 优化3次   使用分布式锁 （可重入锁  读写锁）
    private Map<String, List<Catelog2Vo>> getCatalogJson2() {

//        ValueOperations<String, String> ops = redisTemplate.opsForValue();
//        String catalogJson = ops.get("catalogJson");
        // 给缓存中放json字符串，拿出json字符串，还要逆转为能用的对象类型【序列化与反序列化】
        // 1、加入缓存逻辑
        String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
        if (StringUtils.isEmpty(catalogJSON)){

            System.out.println("缓存没有命中----------，将要查询数据库");

            // 2、缓存中没有，查询数据库
            // 优化2 - 使用本地锁
//            Map<String, List<Catelog2Vo>> catelogJsonFromDB = getCatalogJsonFromDb();
            // 优化3 - 使用分布式锁
            Map<String, List<Catelog2Vo>> catelogJsonFromDB = getCatalogJsonFromDbWithRedissonLock();

            // 3、将查到的数据再放入缓存，将对象转为JSON在缓存中
            // 对象转成JSON
            String jsonString = JSON.toJSONString(catelogJsonFromDB);
            redisTemplate.opsForValue().set("catalogJSON",jsonString,1,TimeUnit.DAYS);

            return catelogJsonFromDB;
        }

        System.out.println("缓存命中---------");

//        // JSON字符串转对象
        Map<String, List<Catelog2Vo>> result =
                JSON.parseObject(catalogJSON,new TypeReference<Map<String, List<Catelog2Vo>>>(){});

        return result;
    }

    // 优化三级分类 - 只查询数据库里的数据 - 使用本地锁
    private  Map<String, List<Catelog2Vo>> getCatalogJsonFromDb() {
        // 本地 + 加锁 可以解决 只有一条线程查询数据库，其他线程都是走缓存
        synchronized(this){
            Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
            return dataFromDb;
        }
    }

    // 递归查询  参数1 集合 父级id
    private List<CategoryEntity> getParent_cid(List<CategoryEntity> selectList, Long parent_cid) {
        List<CategoryEntity> collect = selectList.stream().filter(item -> {
            return item.getParentCid() == parent_cid;
        }).collect(Collectors.toList());
        return collect;
        // return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()));
    }


    /**
     * 缓存里的数据如何和数据库的数据保持一致？？
     * 缓存数据一致性
     * 1)、双写模式
     * 2)、失效模式
     * @return
     */
    private Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedissonLock() {

        // 1、占分布式锁。去redis占坑
        // 创建读锁
//        RReadWriteLock readWriteLock = redisson.getReadWriteLock("catalogJson-lock");
//
//        RLock rLock = readWriteLock.readLock();
//
//        Map<String, List<Catelog2Vo>> dataFromDb = null;
//        try {
//            rLock.lock();
//            // 加锁成功...执行业务
//            dataFromDb = getDataFromDb();
//        } finally {
//            rLock.unlock();
//        }

        //1、占分布式锁。去redis占坑  catalogJSON-lock 锁名
        RLock lock = redisson.getLock("catalogJSON-lock");
        lock.lock();

        Map<String, List<Catelog2Vo>> dataFromDb = null;
        try {
            // 加锁成功...执行业务
            dataFromDb = getDataFromDb();
        } finally {
            // 释放锁
            lock.unlock();

        }

        return dataFromDb;

    }


    // 优化三级分类 - 只查询数据库里的数据
    private Map<String, List<Catelog2Vo>> getDataFromDb() {

        // 得到锁之后，还需要去缓存中查询一下，双重检测锁
        String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
        if (!StringUtils.isEmpty(catalogJSON)){
            Map<String, List<Catelog2Vo>> result =
                    JSON.parseObject(catalogJSON,new TypeReference<Map<String, List<Catelog2Vo>>>(){});
            return result;
        }

        // 只查询一次数据库 - 其他的都走缓存
        System.out.println("查询了数据库 -----"+ Thread.currentThread().getName());

        // 查询全部  只查询一次数据库
        List<CategoryEntity> selectList = baseMapper.selectList(null);
        // System.out.println(selectList);
        // 原先的  List<CategoryEntity> level1Categorys = getLevel1Categorys();
        // 查询所有一级分类 （参数1 全部集合 参数2 一级id）
        List<CategoryEntity> level1Category = getParent_cid(selectList, 0L);

        // 2 封装数据
        Map<String, List<Catelog2Vo>> parent_cid = level1Category.stream()
                .collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
                            // 1 每一个的一级分类，查到这个一级分类的二级分类
                            List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
                            // 2 分装上面的结果
                            List<Catelog2Vo> catelog2Vos = null;
                            if (categoryEntities != null) {
                                catelog2Vos = categoryEntities.stream().map(l2 -> {
                                    Catelog2Vo catelog2Vo = new Catelog2Vo(
                                            v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                                    // 1 找当前二级分类的三级分类封装成vo
                                    List<CategoryEntity> level3Catelog = getParent_cid(selectList, l2.getCatId());
                                    if (level3Catelog != null) {
                                        List<Catelog2Vo.Catelog3Vo> collect = level3Catelog.stream().map(l3 -> {
                                            // 2 分装成指定格式
                                            Catelog2Vo.Catelog3Vo catelog3Vo =
                                                    new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                                            return catelog3Vo;
                                        }).collect(Collectors.toList());
                                        catelog2Vo.setCatalog3List(collect);
                                    }
                                    return catelog2Vo;
                                }).collect(Collectors.toList());
                            }
                            return catelog2Vos;
                        }
                ));

        // 3 查到的数据放入缓存，将对象转为json放在缓存中  有效期为1天
        String jsonString = JSON.toJSONString(parent_cid);
        redisTemplate.opsForValue().set("catalogJSON",jsonString,1,TimeUnit.DAYS);
        return parent_cid;

    }

}
